﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using System.IO;
using log4net;

namespace GenerateDB
{
    class DatabaseSQLHelper : IDisposable
    {
        /// <summary>
        /// Константы и "магические" числа программы
        /// </summary>
        private Settings _settings;

        /// <summary>
        /// Коннектор соединений с базой данных
        /// </summary>
        private DatabaseConnection _dbConnector;

        /// <summary>
        /// Соединение с базой данных
        /// </summary>
        private SqlConnection _sqlConnection;

        /// <summary>
        /// SQL запросы
        /// </summary>
        private SQLQueries _queries;

        private static readonly ILog _log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        public DatabaseSQLHelper(DataBaseInfo dbInfo)
        {
            this.Init(dbInfo);
        }

        /// <summary>
        /// Инициализация класса
        /// </summary>
        /// <param name="dbInfo">Информация для подключения к базе данных</param>
        private void Init(DataBaseInfo dbInfo)
        {
            _settings = new Settings();
            _dbConnector = new DatabaseConnection(dbInfo);
            _queries = new SQLQueries();
            _sqlConnection = _dbConnector.OpenConnection();
        }

        /// <summary>
        /// Создания базы данных для хранения всех служебных таблиц
        /// </summary>
        public void CreateDatabase()
        {
            try
            {
                this.ApplySQL(_queries.CreateDB());
            }
            catch (SqlException ex)
            {
                string error = "Ошибка ошибка создания базы данных.";
                _log.Error(error, ex);
                throw new Exception(error);
            }
        }

        public void DropDatabase()
        {
            try
            {
                this.ApplySQL(_queries.DropDatabase());
            }
            catch (SqlException ex)
            {
                string error = "Ошибка ошибка удаления базы данных.";
                _log.Error(error, ex);
                throw new Exception(error);
            }
        }

        public void UseDatabase()
        {
            try
            {
                this.ApplySQL(_queries.UseDB());
            }
            catch (SqlException ex)
            {
                string error = "Ошибка ошибка use базы данных.";
                _log.Error(error, ex);
                throw new Exception(error);
            }
        }

        /// <summary>
        /// Создание триггера на удаление значений
        /// </summary>
        public void CreateTriggerDeleting()
        {
            try
            {
                this.ApplySQLFromFile(_settings.FileTriggerDeleting);
            }
            catch (IOException ex)
            {
                string error = "Ошибка создания триггера Deleting.";
                _log.Error(error, ex);
                throw new Exception(error);
            }
        }

        /// <summary>
        /// Создания триггера на редактирование значений
        /// </summary>
        public void CreateTriggerUpdating()
        {
            try
            {
                this.ApplySQLFromFile(_settings.FileTriggerUpdating);
            }
            catch (IOException ex)
            {
                string error = "Ошибка создания триггера Updating.";
                _log.Error(error, ex);
                throw new Exception(error);
            }
        }

        public void CreateInsertProcedures()
        {
            try
            {
                FileInfo file = new FileInfo(_settings.FileInsertProcedures);
                StreamReader openFile = file.OpenText();
                string query = openFile.ReadToEnd();
                using (SqlCommand command = new SqlCommand(query, _sqlConnection))
                {
                    command.CommandType = CommandType.Text;
                    command.ExecuteNonQuery();
                }
            }
            catch (SqlException ex)
            {
                string error = "Ошибка создания хранимых процедур для добавления данных.";
                _log.Error(error, ex);
                throw new Exception(error);
            }
        }

        /// <summary>
        /// Добавление в базу данных группы администратора.
        /// </summary>
        public void AddGroupAdmin()
        {
            try
            {
                using (SqlCommand command = new SqlCommand(SQLQueries.InsertGroupAdmin, _sqlConnection))
                {
                    command.Parameters.Add("@Name", SqlDbType.VarChar);
                    command.Parameters.Add("@CanRead", SqlDbType.Bit);
                    command.Parameters.Add("@CanAdd", SqlDbType.Bit);
                    command.Parameters.Add("@CanExport", SqlDbType.Bit);
                    command.Parameters.Add("@CanImport", SqlDbType.Bit);
                    command.Parameters.Add("@CanAddField", SqlDbType.Bit);
                    command.Parameters.Add("@CanDelField", SqlDbType.Bit);
                    command.Parameters.Add("@CanAddUsers", SqlDbType.Bit);
                    command.Parameters.Add("@CanDelUsers", SqlDbType.Bit);
                    command.Parameters.Add("@CanCreateGroups", SqlDbType.Bit);
                    command.Parameters.Add("@CanDelGroups", SqlDbType.Bit);
                    command.Parameters.Add("@CanCreateJournals", SqlDbType.Bit);
                    command.Parameters.Add("@CanDelJournals", SqlDbType.Bit);

                    command.Parameters["@Name"].Value = _settings.DefaultGroupNameAdmin;
                    command.Parameters["@CanRead"].Value = _settings.DefaultAccessRights;
                    command.Parameters["@CanAdd"].Value = _settings.DefaultAccessRights;
                    command.Parameters["@CanExport"].Value = _settings.DefaultAccessRights;
                    command.Parameters["@CanImport"].Value = _settings.DefaultAccessRights;
                    command.Parameters["@CanAddField"].Value = _settings.DefaultAccessRights;
                    command.Parameters["@CanDelField"].Value = _settings.DefaultAccessRights;
                    command.Parameters["@CanAddUsers"].Value = _settings.DefaultAccessRights;
                    command.Parameters["@CanDelUsers"].Value = _settings.DefaultAccessRights;
                    command.Parameters["@CanCreateGroups"].Value = _settings.DefaultAccessRights;
                    command.Parameters["@CanDelGroups"].Value = _settings.DefaultAccessRights;
                    command.Parameters["@CanCreateJournals"].Value = _settings.DefaultAccessRights;
                    command.Parameters["@CanDelJournals"].Value = _settings.DefaultAccessRights;

                    command.ExecuteNonQuery();
                }
            }
            catch (SqlException ex)
            {
                string error = "Ошибка добавления группы админестратора в базу данных.";
                _log.Error(error, ex);
                throw new Exception(error);
            }
        }

        /// <summary>
        /// Добавление в базу группы пользователя и гостя
        /// </summary>
        public void AddGroupUserAndGuest()
        {
            try
            {
                string[] groupsNames = new string[] { _settings.DefaultGroupNameUser, _settings.DefaultGroupNameGuest };
                foreach (var name in groupsNames)
                {
                    using (SqlCommand command = new SqlCommand(SQLQueries.InsertGroupName, _sqlConnection))
                    {
                        command.Parameters.Add("@Name", SqlDbType.VarChar);
                        command.Parameters.Add("@CanRead", SqlDbType.Char);

                        command.Parameters["@Name"].Value = name;
                        command.Parameters["@CanRead"].Value = _settings.DefaultAccessRights;

                        command.ExecuteNonQuery();
                    }
                }
            }
            catch (SqlException ex)
            {
                string error = "Ошибка добавления группы пользователя или гостя в базу данных.";
                _log.Error(error, ex);
                throw new Exception(error);
            }
        }

        /// <summary>
        /// Добавления пользователя. По умолчанию пользователь администратор
        /// </summary>
        public void AddDefaultUser()
        {
            try
            {
                HashingAlgorithms hashing = new HashingAlgorithms();
                using (SqlCommand command = new SqlCommand(SQLQueries.InsertUser, _sqlConnection))
                {
                    command.Parameters.Add("@Name", SqlDbType.VarChar);
                    command.Parameters.Add("@Password", SqlDbType.VarChar);
                    command.Parameters.Add("@GroupsID", SqlDbType.Int);

                    command.Parameters["@Name"].Value = _settings.DefaultUserName;
                    command.Parameters["@Password"].Value = hashing.MD5(_settings.DefaultUserPassword).ToString();
                    command.Parameters["@GroupsID"].Value = _settings.DefaultUsersGroupID;

                    command.ExecuteNonQuery();
                }
            }
            catch (SqlException ex)
            {
                string error = "Ошибка создания пользователя по умолчанию.";
                _log.Error(error, ex);
                throw new Exception(error);
            }
        }

        /// <summary>
        /// Добавление типов данных MSSQL в таблицу Type
        /// </summary>
        public void AddTypes()
        {
            try
            {
                string[] types = new string[] { "int", "binary", "float", "image", "text" };

                foreach (var type in types)
                {
                    string query = String.Format("INSERT INTO loghog..Type (Name) VALUES ('{0}')", type);
                    using (SqlCommand command = new SqlCommand(SQLQueries.InsertType, _sqlConnection))
                    {
                        command.Parameters.Add("@Type", SqlDbType.VarChar);
                        command.Parameters["@Type"].Value = type;
                        command.ExecuteNonQuery();
                    }
                }
            }
            catch (SqlException ex)
            {
                string error = "Ошибка заполнения таблицы Type.";
                _log.Error(error, ex);
                throw new Exception(error);
            }
        }

        /// <summary>
        /// Выполнение SQL кода из файла
        /// </summary>
        /// <param name="filename">Путь к файлу</param>
        public void ApplySQLFromFile(string filename)
        {
            FileInfo file = new FileInfo(filename);
            StreamReader openFile = file.OpenText();
            string query = openFile.ReadToEnd();
            this.ApplySQL(query);
        }

        /// <summary>
        /// Выполняет SQL запросы на создание таблиц и базы данных
        /// </summary>
        /// <param name="query">SQL запрос</param>
        public void ApplySQL(string query)
        {
            try
            {
                using (SqlCommand command = new SqlCommand(query, _sqlConnection))
                {
                    command.ExecuteNonQuery();
                }
            }
            catch (SqlException ex)
            {
                string error = String.Format("Ошибка выполнения SQL кода '{0}'.", query);
                _log.Error(error, ex);
                throw new Exception(error + " Возможно код содержит ошибку.");
            }
        }

        /// <summary>
        /// Добавление названия журнала в базу данных. Название указывается в xml описании.
        /// </summary>
        public void AddNameJournal(string name, bool encryption)
        {
            try
            {
                using (SqlCommand command = new SqlCommand(SQLQueries.InsetJournalName, _sqlConnection))
                {
                    command.Parameters.Add("@Name", SqlDbType.VarChar);
                    command.Parameters.Add("@UserID", SqlDbType.Int);
                    command.Parameters.Add("@Encryption", SqlDbType.Bit);

                    command.Parameters["@Name"].Value = name;
                    command.Parameters["@UserID"].Value = _settings.DefaultUserID;
                    command.Parameters["@Encryption"].Value = encryption;

                    command.ExecuteNonQuery();
                }
            }
            catch (SqlException ex)
            {
                string error = "Ошибка добавления имени журнала в базу данных.";
                _log.Error(error, ex);
                throw new Exception(error);
            }
        }

        /// <summary>
        /// Добавление колонок журнала, взятых из xml оисания, в базу данных.
        /// </summary>
        public void AddColumnsJournal(string journalName, List<ColumnInfo> columns)
        {
            try
            {
                foreach (var column in columns)
                {
                    string columnName = System.Convert.ToString(column.Name);
                    string columnNameRus = System.Convert.ToString(column.NameRus);
                    string description = System.Convert.ToString(column.Description);
                    DateTime date = DateTime.Now;
                    int idType = System.Convert.ToInt32(this.GetIdByName(column.Type, SQLQueries.SelectType));

                    using (SqlCommand command = new SqlCommand(SQLQueries.InsetrField, _sqlConnection))
                    {
                        command.Parameters.Add("@Name", SqlDbType.VarChar);
                        command.Parameters.Add("@NameRus", SqlDbType.VarChar);
                        command.Parameters.Add("@Description", SqlDbType.VarChar);
                        command.Parameters.Add("@DataCreation", SqlDbType.DateTime);
                        command.Parameters.Add("@TypeID", SqlDbType.Int);

                        command.Parameters["@Name"].Value = columnName;
                        command.Parameters["@NameRus"].Value = columnNameRus;
                        command.Parameters["@Description"].Value = description;
                        command.Parameters["@DataCreation"].Value = date;
                        command.Parameters["@TypeID"].Value = idType;

                        command.ExecuteNonQuery();
                    }

                    int idJournal = this.GetIdByName(journalName, SQLQueries.SelectIdJournal);
                    int idColumn = this.GetIdByName(columnName, SQLQueries.SelectIdColumn);
                    this.AddIdColumnInJournalColumns(idJournal, idColumn);
                }
            }
            catch (SqlException ex)
            {
                string error = "Ошибка добавления колонок в базу данных.";
                _log.Error(error, ex);
                throw new Exception(error);
            }
        }

        /// <summary>
        /// Получение id по имени атрибута
        /// </summary>
        /// <param name="name">Название атрибута</param>
        /// <param name="query">Sql запрос на извлечение данных</param>
        /// <returns>Возвращает номер типа в таблице Type</returns>
        private int GetIdByName(string name, string query)
        {
            int result = 0;
            try
            {
                using (SqlCommand command = new SqlCommand(query, _sqlConnection))
                {
                    command.Parameters.Add("@Name", SqlDbType.VarChar);
                    command.Parameters["@Name"].Value = name;

                    result = (int)command.ExecuteScalar();
                }
                return result;
            }
            catch (SqlException ex)
            {
                string error = "Ошибка извлечения ID по имени атрибута.";
                _log.Error(error, ex);
                throw new Exception(error);
            }
        }

        private void AddIdColumnInJournalColumns(int idJournal, int idColumn)
        {
            try
            {
                using (SqlCommand command = new SqlCommand(SQLQueries.InsetrIdJournalIdColumns, _sqlConnection))
                {
                    command.Parameters.AddWithValue("@IdJournal", SqlDbType.Int);
                    command.Parameters.AddWithValue("@IdColumn", SqlDbType.Int);

                    command.Parameters["@IdJournal"].Value = idJournal;
                    command.Parameters["@IdColumn"].Value = idColumn;

                    command.ExecuteNonQuery();
                }
            }
            catch (SqlException ex)
            {
                string error = "Ошибка добавления id колонки и id журнала в таблицу Journal_Field.";
                _log.Error(error, ex);
                throw new Exception(error);
            }
        }

        public void Dispose()
        {
            _dbConnector.CloseConnection();
        }
    }
}
